﻿#region Copyright

// All Material Copyright © 2010 Dynamic Concepts Development Corp., All Rights Reserved.
//      Portions Copyright © 1984-2009 Dynamic Concepts Development Corp., All Rights Reserved.
// For further information please contact: SoftwareSupport@dynconcepts.com
// Dynamic Concepts Development Corp.  311 W 57th St. Suite #422, New York, NY 10019

#endregion

#region Using References

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;

#endregion

namespace DynConDevCorp.OpenArch.SupportLibrary.Serialization
{
    public class CustomObjectSerializer
    {
        #region Constructors

        static CustomObjectSerializer()
        {
            Register(new ByConvertReaderWriter(typeof (string)));
            Register(new ByConvertReaderWriter(typeof (int)));
            Register(new ByConvertReaderWriter(typeof (long)));
            Register(new ByConvertReaderWriter(typeof (double)));
            Register(new ByConvertReaderWriter(typeof (DateTime)));
            Register(new UriReaderWriter());
        }

        public CustomObjectSerializer(Type rootType)
        {
            r_RootType = rootType;
            GenerateSchemaIfMissing(rootType);
        }

        #endregion

        #region Public Methods

        public object Deserialize(StreamReader reader, Type itemType)
        {
            string buffer = reader.ReadToEnd().Replace("\r", "").Replace("\n", "");

            TypeSerializationDescriptor typeDescriptor = sr_TypeDescriptors[itemType];
            object retVal = Deserialize("Root", typeDescriptor, buffer);
            return retVal;
        }

        public static bool IsReaderWriterRegistered(Type dataType)
        {
            return sr_ReaderWriters.ContainsKey(dataType);
        }

        public static void Register(IReaderWriter readerWriter)
        {
            sr_ReaderWriters.Add(readerWriter.DataType, readerWriter);
        }

        public void Serialize(StreamWriter writer, Object obj)
        {
            if (!r_RootType.IsAssignableFrom(obj.GetType()))
            {
                throw new Exception();
            }
            TypeSerializationDescriptor typeDescriptor = sr_TypeDescriptors[r_RootType];
            Serialize(writer, obj, "Root", typeDescriptor, 0);
            return;
        }

        #endregion

        #region Private Methods

        private object Deserialize(string name, TypeSerializationDescriptor typeDescriptor, string buffer)
        {
            string typeContent = ParseContent(buffer, name);
            string contentTypeName = GetTypeFromContent(ref typeContent);
            object retVal = Activator.CreateInstance(typeDescriptor.ItemType);
            foreach (PropertySerializationDescriptor propertyDescriptor in typeDescriptor.PropertyDescriptors)
            {
                object value;
                if (IsReaderWriterRegistered(propertyDescriptor.DataType))
                {
                    string openingToken = GetOpeningToken(typeContent);
                    string propertyContent = ParseContent(typeContent, propertyDescriptor.PropertyName);
                    value = StringToValue(propertyDescriptor.DataType, propertyContent);
                }
                else
                {
                    TypeSerializationDescriptor propertyDataTypeDescriptor = sr_TypeDescriptors[propertyDescriptor.DataType];
                    value = Deserialize(propertyDescriptor.PropertyName, propertyDataTypeDescriptor, typeContent);
                }
                propertyDescriptor.PropertyInfo.SetValue(retVal, value, new object[] {});
            }
            return retVal;
        }

        private TypeSerializationDescriptor GenerateSchema(Type elementType)
        {
            object[] classAttributes = elementType.GetCustomAttributes(true);
            foreach (object classAttribute in classAttributes)
            {
                XmlTypeAttribute asTypeAttribute = classAttribute as XmlTypeAttribute;
                if (asTypeAttribute != null)
                {
                    TypeSerializationDescriptor typeDescriptor = new TypeSerializationDescriptor();
                    typeDescriptor.ItemType = elementType;
                    typeDescriptor.TypeAttribute = asTypeAttribute;
                    GenerateSchema(typeDescriptor);
                    return typeDescriptor;
                }
            }
            return null;
        }

        private void GenerateSchema(TypeSerializationDescriptor typeDescriptor)
        {
            PropertyInfo[] properties = typeDescriptor.ItemType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (PropertyInfo property in properties)
            {
                if (property.CanRead && property.CanWrite)
                {
                    object[] propertyAttributes = property.GetCustomAttributes(true);
                    bool includePropery = false;
                    XmlElementAttribute asElementAttribute = null;
                    XmlIgnoreAttribute asIgnoreAttribute = null;
                    foreach (object propertyAttribute in propertyAttributes)
                    {
                        asElementAttribute = propertyAttribute as XmlElementAttribute;
                        if (asElementAttribute != null)
                        {
                            includePropery = true;
                        }
                        asIgnoreAttribute = propertyAttribute as XmlIgnoreAttribute;
                        if (asIgnoreAttribute != null)
                        {
                        }
                    }
                    if (includePropery)
                    {
                        PropertySerializationDescriptor propertyDescriptor = new PropertySerializationDescriptor
                                                                                 {
                                                                                     PropertyName = property.Name,
                                                                                     ContainingType = typeDescriptor,
                                                                                     DataType = property.PropertyType,
                                                                                     ElementAttribute = asElementAttribute,
                                                                                     IgnoreAttribute = asIgnoreAttribute,
                                                                                     PropertyInfo = property
                                                                                 };
                        typeDescriptor.PropertyDescriptors.Add(propertyDescriptor);
                        //QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Propessing Property({0}, {1}) - Entered", typeDescriptor.ItemType.Name, property.Name);
                        if (!IsReaderWriterRegistered(propertyDescriptor.DataType))
                        {
                            GenerateSchemaIfMissing(propertyDescriptor.DataType);
                        }
                        //QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "Propessing Property({0}, {1}) - Exited", typeDescriptor.ItemType.Name, property.Name);
                    }
                }
            }
        }

        private void GenerateSchemaIfMissing(Type itemType)
        {
            lock (sr_SyncRoot)
            {
                // QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "GenerateSchemaIfMissing({0}) - Entered", itemType.Name);
                if (!sr_TypeDescriptors.ContainsKey(itemType))
                {
                    if (!sr_PendingSchemas.Contains(itemType))
                    {
                        sr_PendingSchemas.Add(itemType);
                        TypeSerializationDescriptor typeDescriptor = GenerateSchema(itemType);
                        if (typeDescriptor != null)
                            sr_TypeDescriptors.Add(typeDescriptor.ItemType, typeDescriptor);
                        sr_PendingSchemas.Remove(itemType);
                    }
                }
                // QueuedLogger.GlobalInstance.Log(QueuedLogger.MessageSeverity.Debug, "GenerateSchemaIfMissing({0}) - Exited", itemType.Name);
            }
        }

        ////private string m_Buffer;
        ////private int m_Index; 
        ////private Char GetChar()
        ////{
        ////    Char ch = ' ';
        ////    do
        ////    {
        ////        ch = m_Buffer[m_Index++];
        ////    } while ((ch == '\r') || (ch == '\n'));
        ////    return ch;
        ////}

        private String GetOpeningToken(string buffer)
        {
            String retVal = String.Empty;
            int index = 0;
            Char ch = buffer[index++];
            if (ch != '<') throw new Exception();
            while ((ch = buffer[index++]) != '>')
                retVal += ch;
            return retVal;
        }

        private static string GetTypeFromContent(ref string typeContent)
        {
            string typeAttributeName = "Type=\"";
            string typeName = String.Empty;
            if (typeContent.Trim().StartsWith(typeAttributeName))
            {
                int startIndex = typeContent.IndexOf(typeAttributeName) + typeAttributeName.Length;
                const string closingKey = "\">";
                int endIndex = typeContent.IndexOf(closingKey);
                typeName = typeContent.Substring(startIndex, endIndex - startIndex);
                typeContent = typeContent.Substring(endIndex + closingKey.Length);
            }
            return typeName;
        }

        private string ParseContent(string buffer, string token)
        {
            string openingToken = String.Format("<{0}", token);
            int startIndex = buffer.IndexOf(openingToken) + openingToken.Length + 1;
            string closingToken = String.Format("</{0}>", token);
            int endIndex = buffer.IndexOf(closingToken);
            string content = String.Empty;
            if ((startIndex >= 0) && (endIndex > startIndex))
            {
                content = buffer.Substring(startIndex, endIndex - startIndex);
            }
            return content;
        }

        private void Serialize(StreamWriter writer, object obj, string name, TypeSerializationDescriptor typeDescriptor, int level)
        {
            WriteLine(writer, level*3, String.Format("<{0} Type=\"{1}\">", name, typeDescriptor.ItemType.Name));
            foreach (PropertySerializationDescriptor propertyDescriptor in typeDescriptor.PropertyDescriptors)
            {
                object value = propertyDescriptor.PropertyInfo.GetValue(obj, new object[] {});
                if (IsReaderWriterRegistered(propertyDescriptor.DataType))
                {
                    string content = ValueToString(propertyDescriptor.DataType, value);
                    if (content.IndexOfAny(new[] {'\r', '\n'}) > 0)
                    {
                        WriteLine(writer, level*3 + 1, String.Format("<{0}>", propertyDescriptor.PropertyName));
                        WriteLine(writer, level*3 + 2, String.Format("{0}", content));
                        WriteLine(writer, level*3 + 1, String.Format("</{0}>", propertyDescriptor.PropertyName));
                    }
                    else
                    {
                        WriteLine(writer, level*3 + 2, String.Format("<{0}>{1}</{0}>", propertyDescriptor.PropertyName, content));
                    }
                }
                else
                {
                    TypeSerializationDescriptor propertyDataTypeDescriptor = sr_TypeDescriptors[propertyDescriptor.DataType];
                    Serialize(writer, value, propertyDescriptor.PropertyName, propertyDataTypeDescriptor, level + 1);
                }
            }
            WriteLine(writer, level*4, String.Format("</{0}>", name));
        }

        private static object StringToValue(Type type, String src)
        {
            IReaderWriter readerWriter = sr_ReaderWriters[type];
            return readerWriter.ConvertFromString(src);
        }

        private static string ValueToString(Type type, object src)
        {
            IReaderWriter readerWriter = sr_ReaderWriters[type];
            return readerWriter.ConvertToString(src);
        }

        private void WriteLine(StreamWriter writer, int indent, string value)
        {
            string format = String.Format("{{0:{0}}}", indent);
            writer.WriteLine(format, value);
        }

        #endregion

        #region Static Fields and Constants

        private static readonly List<Type> sr_PendingSchemas = new List<Type>();

        private static readonly ReaderWriterCollection sr_ReaderWriters = new ReaderWriterCollection();

        private static readonly object sr_SyncRoot = new object();

        private static readonly Dictionary<Type, TypeSerializationDescriptor> sr_TypeDescriptors = new Dictionary<Type, TypeSerializationDescriptor>();

        #endregion

        #region Instance Fields (ReadOnly)

        private readonly Type r_RootType;

        #endregion
    }
}